/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * CogTool is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool.uimodel; import java.io.ByteArrayInputStream; import java.util.Comparator; import java.util.EventObject; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.SWTGraphics; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import edu.cmu.cs.hcii.cogtool.model.AShape; import edu.cmu.cs.hcii.cogtool.model.DoubleRectangle; import edu.cmu.cs.hcii.cogtool.model.IWidget; import edu.cmu.cs.hcii.cogtool.model.ShapeOval; import edu.cmu.cs.hcii.cogtool.model.ShapeRoundedRectangle; import edu.cmu.cs.hcii.cogtool.model.SkinType; import edu.cmu.cs.hcii.cogtool.model.Widget; import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes; import edu.cmu.cs.hcii.cogtool.model.WidgetType; import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil; import edu.cmu.cs.hcii.cogtool.util.AlertHandler; import edu.cmu.cs.hcii.cogtool.util.IAttributed; import edu.cmu.cs.hcii.cogtool.util.NullSafe; import edu.cmu.cs.hcii.cogtool.util.OSUtils; import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities; import edu.cmu.cs.hcii.cogtool.util.WindowUtil; import edu.cmu.cs.hcii.cogtool.view.View; /** * The graphical widget class is the "view" of a Widget. * Specifically it is the Draw2D implementation of the widget. * * Custom optimizations are made for rendering offline and updating the display. * @author alexeiser/weian * */ public class GraphicalWidgetBase<T extends IWidget> extends AbstractGraphicalSource<T> implements GraphicalWidget<T> { // Factory methods /** * Factory method that generates a clipper corresponding to a given shape * @param shape the shape to clip to * @return a clipper appropriate for the shape */ protected static GraphicalWidgetClipper getClipperForShape(AShape shape) { if (shape instanceof ShapeRoundedRectangle) { return new GraphicalWidgetClipper.RoundedClipper((ShapeRoundedRectangle) shape); } if (shape instanceof ShapeOval) { return GraphicalWidgetClipper.OVAL_CLIPPER; } // By default, do not clip return GraphicalWidgetClipper.TRIVIAL_CLIPPER; } /** * Factory method that generates a painter corresponding to a given widget type * @param type the widget type, or null to indicate an unrendered widget * @return a renderer appropriate to the widget type */ protected GraphicalWidgetRenderer getRendererForType(WidgetType type, SkinType skin, boolean renderSkin) { if ((type == WidgetType.Noninteractive) || (type == WidgetType.Text)) { return new LabelRenderer(this.attrOverride); } if ((type == WidgetType.Link) && renderSkin) { return new LinkWireRenderer(this, this.attrOverride); } if (renderSkin && (skin == SkinType.WireFrame)) { if (type == WidgetType.Button) { return new ItemWireRenderer(ItemWireRenderer.LABEL_CENTER, this, this.attrOverride); } if (type == WidgetType.Check) { return new ChoiceWireRenderer(ChoiceWireRenderer.CHECK, this, this.attrOverride); } if (type == WidgetType.Radio) { return new ChoiceWireRenderer(ChoiceWireRenderer.RADIO, this, this.attrOverride); } if (type == WidgetType.TextBox) { return new TextBoxRenderer(ItemWireRenderer.LABEL_LEFT, this, this.attrOverride); } if (type == WidgetType.Submenu) { return new SubmenuWireRenderer(this, this.attrOverride); } if (type == WidgetType.PullDownList) { return new PulldownListWireRenderer(this, this.attrOverride); } if ((type == WidgetType.PullDownItem) || (type == WidgetType.ListBoxItem) || (type == WidgetType.Menu) || (type == WidgetType.MenuItem) || (type == WidgetType.Graffiti)) { return new ItemWireRenderer(ItemWireRenderer.LABEL_LEFT, this, this.attrOverride); } } if ((! renderSkin) || (skin == SkinType.None)) { if (type == WidgetType.TextBox) { return new LabelRenderer(GraphicalWidgetRenderer.LABEL_LEFT, this.attrOverride); } // By default, just paint the widget's title if (type == WidgetType.Button) { return new LabelRenderer(this.attrOverride); } if (type.equals(WidgetType.Submenu)) { return new SubmenuLabelRenderer(this.attrOverride); } if (type == WidgetType.PullDownList) { LabelRenderer lRenderer = new LabelRenderer(GraphicalWidgetRenderer.LABEL_LEFT, this.attrOverride) { @Override public void updateData() { GraphicalWidget<?> gw = (GraphicalWidget<?>) getParent(); IWidget widget = gw.getModel(); setText(RendererSupport.getPullDownSelectedTitle(widget, lAttrOverride)); } }; return lRenderer; } if ((type == WidgetType.PullDownItem) || (type == WidgetType.ListBoxItem) || (type == WidgetType.MenuItem)) { return new LabelRenderer(GraphicalWidgetRenderer.LABEL_LEFT, this.attrOverride) { @Override public void updateData() { GraphicalWidget<?> gw = (GraphicalWidget<?>) getParent(); IWidget w = gw.getModel(); Object sepValue = w.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); boolean isSep = NullSafe.equals(WidgetAttributes.IS_SEPARATOR, sepValue); setText(isSep ? GraphicalWidgetRenderer.SEPARATOR_STRING : w.getTitle()); } }; } return new LabelRenderer(GraphicalWidgetRenderer.LABEL_LEFT, this.attrOverride); } // Image renders - either Nine-Part or zoomed image if (type == WidgetType.Button) { return new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_CENTER, NinePartRenderer.RENDER_FULL, this.attrOverride) { @Override public void updateData() { IWidget attributed = getModel(); boolean isSelected = RendererSupport.isSelected(attributed, attrOverride); Object toggleable = attributed.getAttribute(WidgetAttributes.IS_TOGGLEABLE_ATTR); if (isSelected && NullSafe.equals(toggleable, WidgetAttributes.IS_TOGGLEABLE) && (altSet != null)) { imageParts = altSet; } else { imageParts = defaultSet; } } }; } if (type == WidgetType.Check) { NinePartRenderer boxRenderer = new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_LEFT_SQUARE, this.attrOverride); ZoomRenderer checkRenderer = new ZoomRenderer(this, type.getKeyName(), skin.getKeyName(), ZoomRenderer.NO_LABEL, ZoomRenderer.RENDER_LEFT_SQUARE, this.attrOverride); GraphicalWidgetRenderer[] rendererArray = { boxRenderer, checkRenderer }; return new CheckboxImageRenderer(rendererArray, 0); } if (type == WidgetType.Radio) { return new ZoomRenderer(this, type.getKeyName(), skin.getKeyName(), ZoomRenderer.LABEL_LEFT, ZoomRenderer.RENDER_LEFT_SQUARE, this.attrOverride) { @Override public void updateData() { boolean isSelected = RendererSupport.isRadioSelected(getModel(), attrOverride); if (isSelected && (altImage != null)) { image = altImage; } else { image = defaultImage; } } }; } if (type == WidgetType.TextBox) { return new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_FULL, this.attrOverride); } // MacOS pulldownlist only! if ((skin == SkinType.MacOSX) && (type == WidgetType.PullDownList)) { ZoomRenderer arrowRenderer = new ZoomRenderer(this, type.getKeyName(), skin.getKeyName(), ZoomRenderer.LABEL_LEFT, ZoomRenderer.RENDER_RIGHT_SQUARE, this.attrOverride); NinePartRenderer leftRenderer = new NinePartRenderer(this, type.getKeyName() + "_left", skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_LEFT_FILL, this.attrOverride); NinePartRenderer rightRenderer = new NinePartRenderer(this, type.getKeyName() + "_right", skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_RIGHT_SQUARE, this.attrOverride) { @Override public void updateData() { GraphicalWidget<?> gw = (GraphicalWidget<?>) getParent(); IWidget widget = gw.getModel(); setText(RendererSupport.getPullDownSelectedTitle(widget, attrOverride)); } }; GraphicalWidgetRenderer[] rendererArray = { leftRenderer, rightRenderer, arrowRenderer }; return new GraphicalWidgetRenderer.CompositeRenderer(rendererArray, 1); } // WinXP pulldownlist only! if ((skin == SkinType.WinXP) && (type == WidgetType.PullDownList)) { ZoomRenderer arrowRenderer = new ZoomRenderer(this, type.getKeyName(), skin.getKeyName(), ZoomRenderer.LABEL_LEFT, ZoomRenderer.RENDER_RIGHT_SQUARE, this.attrOverride); NinePartRenderer leftRenderer = new NinePartRenderer(this, type.getKeyName() + "_left", skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_LEFT_FILL, this.attrOverride); NinePartRenderer rightRenderer = new NinePartRenderer(this, type.getKeyName() + "_right", skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_RIGHT_SQUARE, this.attrOverride) { @Override public void updateData() { GraphicalWidget<?> gw = (GraphicalWidget<?>) getParent(); IWidget widget = gw.getModel(); setText(RendererSupport.getPullDownSelectedTitle(widget, attrOverride)); } }; GraphicalWidgetRenderer[] rendererArray = { leftRenderer, rightRenderer, arrowRenderer }; return new GraphicalWidgetRenderer.CompositeRenderer(rendererArray, 1); } // Pulldown lists for non-macOSX or WinXP skins only if (type == WidgetType.Submenu) { ZoomRenderer arrowRenderer = new ZoomRenderer(this, type.getKeyName(), skin.getKeyName(), ZoomRenderer.LABEL_LEFT, ZoomRenderer.RENDER_RIGHT_SQUARE, this.attrOverride); NinePartRenderer backgroundRenderer = new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_FULL, this.attrOverride); GraphicalWidgetRenderer[] rendererArray = { backgroundRenderer, arrowRenderer }; return new GraphicalWidgetRenderer.CompositeRenderer(rendererArray, 1); } if (type == WidgetType.PullDownList) { ZoomRenderer arrowRenderer = new ZoomRenderer(this, type.getKeyName(), skin.getKeyName(), ZoomRenderer.LABEL_LEFT, ZoomRenderer.RENDER_RIGHT_SQUARE, this.attrOverride) { @Override public void updateData() { GraphicalWidget<?> gw = (GraphicalWidget<?>) getParent(); IWidget widget = gw.getModel(); setText(RendererSupport.getPullDownSelectedTitle(widget, attrOverride)); } }; NinePartRenderer backgroundRenderer = new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_FULL, this.attrOverride); GraphicalWidgetRenderer[] rendererArray = { backgroundRenderer, arrowRenderer }; return new GraphicalWidgetRenderer.CompositeRenderer(rendererArray, 1); } if ((type == WidgetType.MenuItem) || (type == WidgetType.PullDownItem) || (type == WidgetType.ListBoxItem)) { NinePartRenderer itemRenderer = new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_FULL, this.attrOverride); ZoomRenderer sepRenderer = new ZoomRenderer(this, "separator", skin.getKeyName(), ZoomRenderer.NO_LABEL, ZoomRenderer.RENDER_FULL, this.attrOverride); GraphicalWidgetRenderer[] rendererArray = { itemRenderer, sepRenderer }; return new GraphicalWidgetRenderer.ChoiceMultiRenderer(rendererArray, 0) { @Override public void updateData() { Object isSep = getModel().getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(isSep, WidgetAttributes.IS_SEPARATOR)) { curRenderer = 1; } else { curRenderer = 0; } } }; } return new NinePartRenderer(this, type.getKeyName(), skin.getKeyName(), NinePartRenderer.LABEL_LEFT, NinePartRenderer.RENDER_FULL, this.attrOverride); } // Renderers as inner classes /** * Paints only the title text * @author weianw */ protected class LabelRenderer extends Label implements GraphicalWidgetRenderer { protected DefaultSEUIModel lAttrOverride; public LabelRenderer(DefaultSEUIModel ovr) { this(LABEL_CENTER, ovr); } public LabelRenderer(int labelStyle, DefaultSEUIModel ovr) { this.lAttrOverride = ovr; if (labelStyle == NO_LABEL) { setVisible(false); } else if (labelStyle == LABEL_LEFT) { setTextPlacement(PositionConstants.LEFT); setTextAlignment(PositionConstants.LEFT); } else if (labelStyle == LABEL_RIGHT) { setTextPlacement(PositionConstants.RIGHT); setTextAlignment(PositionConstants.RIGHT); } } public void paintForeground(Graphics g) { if (isVisible()) { g.pushState(); try { super.paintFigure(g); } finally { g.popState(); } } } public void paintMidground(Graphics g) { clipper.fillShape(g, getBounds()); } public void paintBackground(Graphics g) { // Nothing to do } protected static final String PADDING = " "; @Override public void setText(String text) { super.setText(PADDING + text); } @Override public String getText() { return super.getText().substring(PADDING.length()); } @Override public IFigure getParent() { return GraphicalWidgetBase.this; } public void updateData() { // Do nothing by default } } protected class SubmenuLabelRenderer extends LabelRenderer { protected static final String SUBMENU_INDICATOR = " >"; public SubmenuLabelRenderer(DefaultSEUIModel ovr) { super(LABEL_LEFT, ovr); } @Override public void setText(String text) { super.setText(text + SUBMENU_INDICATOR); } @Override public String getText() { String text = super.getText(); return text.substring(0, text.length() - SUBMENU_INDICATOR.length()); } } protected static class SubmenuWireRenderer extends ItemWireRenderer { public SubmenuWireRenderer(GraphicalWidgetBase<?> parent, DefaultSEUIModel ovr) { super(LABEL_LEFT, parent, true, ovr); } @Override protected void paintRightIcon(Graphics g) { Rectangle bds = getBounds(); int x = bds.width - bds.height - 2; int y = 4; int w = bds.height - 6; int h = bds.height - 8; if (x < 0) { x = 1; } if (y >= bds.height) { y = bds.height; } if (w <= 0) { w = 1; } if (h <= 0) { h = 1; } int[] triangle = { x, y, x + w, y + (h/2), x, y + h }; g.drawPolygon(triangle); } } protected static class PulldownListWireRenderer extends ItemWireRenderer { public PulldownListWireRenderer(GraphicalWidgetBase<?> parent, DefaultSEUIModel ovr) { super(LABEL_LEFT, parent, true, ovr); } @Override protected void paintRightIcon(Graphics g) { Rectangle bds = getBounds(); int x = bds.width - bds.height - 2; int y = 4; int w = bds.height - 6; int h = bds.height - 8; if (x < 0) { x = 1; } if (y >= bds.height) { y = bds.height; } if (w <= 0) { w = 1; } if (h <= 0) { h = 1; } int[] triangle = { x, y, x + w, y, x + (w/2), y + h }; g.drawPolygon(triangle); } @Override public void updateData() { GraphicalWidget<?> gw = (GraphicalWidget<?>) getParent(); IWidget widget = gw.getModel(); setText(RendererSupport.getPullDownSelectedTitle(widget, attrOverride)); } } // Fields // TODO (?): Currently the regenerateCaches code is inefficient because it // creates an image for each widget that is displayed. The solution to this // presumably would be to save only the image data and not the image itself, // and only create and dispose of the image when necessary. However, for // some reason that method causes an OutOfMemory error much earlier than // SWT runs out of handles when using the original method, so I changed it // back. // protected static final ImageData DEFAULT_DATA; // static { // Image img = new Image(null, 1, 1); // DEFAULT_DATA = img.getImageData(); // img.dispose(); // } public static class GraphicalWidgetLevelComparator implements Comparator<GraphicalWidget<?>> { /** * No state, so only one instance is ever needed. */ public static GraphicalWidgetLevelComparator ONLY = new GraphicalWidgetLevelComparator(); protected GraphicalWidgetLevelComparator() { } /** * This comparator returns 1 if */ public int compare(GraphicalWidget<?> w1, GraphicalWidget<?> w2) { return Widget.WidgetLevelComparator.ONLY.compare(w1.getModel(), w2.getModel()); } } protected boolean selected; protected GraphicalWidgetClipper clipper; protected GraphicalWidgetRenderer renderer; protected boolean fast = true; protected Color widgetColor = GraphicsUtil.DEFAULT_COLOR; // never null protected Color midgroundColor; protected Color selectedColor; // serializes access to the 2 cache flags protected Object flagLock = new Object(); protected boolean cachedMidgroundDirty; protected boolean cachedBackgroundDirty; // protected ImageData cachedMidgroundData = DEFAULT_DATA; protected Image cachedMidground = new Image(null, 1, 1); // never null protected Image cachedBackground; protected int desiredRolloverCursor; protected DefaultSEUIModel attrOverride; protected static int determineProperCursor(IWidget w, int rolloverCursor) { if ((rolloverCursor == WindowUtil.DRAW_CURSOR) && (w.getWidgetType() == WidgetType.Noninteractive)) { return WindowUtil.SELECT_CURSOR; } Object isSep = w.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, isSep)) { return WindowUtil.SELECT_CURSOR; } return rolloverCursor; } // Constructor public GraphicalWidgetBase(T m, int color, boolean supportToolTip, int rolloverCursor, FrameUIModel displayComputer, DefaultSEUIModel override) { super(supportToolTip, determineProperCursor(m, rolloverCursor), displayComputer); this.midgroundColor = this.widgetColor; this.selectedColor = getOppositeColor(this.midgroundColor); // Do this before setSourceModel (tries to reset the cursor!) this.desiredRolloverCursor = rolloverCursor; this.attrOverride = override; setSourceModel(m); if (color != GraphicsUtil.defaultWidgetColor) { setColor(color); } setFastMode(false); AlertHandler handler = new AlertHandler() { public void handleAlert(EventObject alert) { renderer.updateData(); regenerateCaches(); repaint(); } }; model.addHandler(this, IAttributed.AttributeChange.class, handler); model.addHandler(this, IAttributed.AuthorityChange.class, handler); if (this.attrOverride != null) { this.attrOverride.addHandler(this, DefaultSEUIModel.CurrentOverrideChange.class, handler); } } @Override protected String getTypeDescription() { return model.getWidgetType().toString(); } // Administrative stuff public void addChangeHandler(AlertHandler handler, Class<? extends EventObject> eventClass) { model.addHandler(this, eventClass, handler); } public void removeChangeHandler(AlertHandler handler, Class<? extends EventObject> eventClass) { model.removeHandler(eventClass, handler); } public void setSelected(boolean isSelected) { if (this.selected != isSelected) { this.selected = isSelected; // if (OSUtils.WINDOWS) { // XXX: Primarily want to regenerate caches. // On Windows, can't just set alpha in // the GC for some reason, it doesn't // apply to an image with transparency // Only way to keep the clip region and // the transparency is to reclip when // the selection changes. // since the transparency can only // be set on resize. // now that selection changes color instead of alpha, Macs need to // regenerate caches also. synchronized(this.flagLock) { this.cachedMidgroundDirty = true; } this.renderer.updateData(); regenerateCaches(); // } } } public boolean isSelected() { return this.selected; } @Override protected void updateAttributes() { // Inherit attributes from model updateType(); updateShape(); updateImage(); } public Color getMidgroundColor() { return this.midgroundColor; } public GraphicalWidgetRenderer getRenderer() { return this.renderer; } // These methods update our internal state to correspond to that of our model @Override synchronized public void updateTitle() { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedMidgroundDirty = true; } // Update the text in the renderer this.renderer.setText(getModel().getTitle()); this.renderer.updateData(); if (this instanceof GraphicalChildWidget<?, ?>) { GraphicalParentWidget<?, ?> parentFigure = ((GraphicalChildWidget<?, ?>) this).getParentFigure(); parentFigure.getRenderer().updateData(); } regenerateCaches(); } synchronized public void updateType() { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedMidgroundDirty = true; } IWidget w = getModel(); try { // Change the renderer to one that is appropriate for the type this.renderer = getRendererForType(w.getWidgetType(), w.getFrame().getDesign().getSkin(), w.isRendered()); } catch (Exception e) { System.err.println("Error creating widget renderer: " + e); try { this.renderer = getRendererForType(w.getWidgetType(), SkinType.None, w.isRendered()); } catch (Exception ex) { System.err.println("Error creating unskinned widget renderer: " + e); this.renderer = new LabelRenderer(this.attrOverride); } } Dimension widgetExtent = getSize(); this.renderer.setSize(widgetExtent.width, widgetExtent.height); this.renderer.setText(w.getTitle()); this.renderer.updateData(); setCursor(determineProperCursor(w, this.desiredRolloverCursor)); regenerateCaches(); } synchronized public void updateShape() { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedMidgroundDirty = true; this.cachedBackgroundDirty = true; } // Keep track of some properties that we need later Dimension oldSize = getSize(); GraphicalWidgetClipper oldClipper = this.clipper; AShape s = model.getShape(); DoubleRectangle shapeBds = s.getBounds(); Rectangle figBds = PrecisionUtilities.getDraw2DRectangle(shapeBds); // Inherit properties from our model's shape setBounds(figBds); if ((figBds.width == 0) || (figBds.height == 0)) { if (figBds.width == 0) { figBds.width = 1; } if (figBds.height == 0) { figBds.height = 1; } super.setSize(figBds.width, figBds.height); } this.renderer.setSize(figBds.width, figBds.height); setPreferredSize(figBds.width, figBds.height); this.clipper = getClipperForShape(s); // Decide whether the caches are really dirty if (this.clipper.equals(oldClipper) && getSize().equals(oldSize)) { synchronized(this.flagLock) { this.cachedMidgroundDirty = false; this.cachedBackgroundDirty = false; } } regenerateCaches(figBds.width, figBds.height); } synchronized public void updateImage() { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedBackgroundDirty = true; } // Confirm that the image is readable (throws an exception if it is not) if (model.getImage() != null) { new ImageData(new ByteArrayInputStream(model.getImage())); } this.renderer.updateData(); regenerateCaches(); } synchronized public void dynamicHighlight(boolean highlight) { boolean changed = false; if (highlight) { if (this.midgroundColor != ColorConstants.gray) { changed = true; this.midgroundColor = ColorConstants.gray; } } else { if (this.midgroundColor != this.widgetColor) { changed = true; this.midgroundColor = this.widgetColor; } } if (changed) { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedMidgroundDirty = true; } regenerateCaches(); repaint(); } } // Setters to manipulate parameters that are not stored in the model @Override synchronized public void setColor(int color) { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedMidgroundDirty = true; } boolean changeMidground = (this.midgroundColor == this.widgetColor); if (this.widgetColor != GraphicsUtil.DEFAULT_COLOR) { this.widgetColor.dispose(); } this.widgetColor = new Color(null, GraphicsUtil.getRGBFromColor(color)); if (changeMidground) { this.midgroundColor = this.widgetColor; this.selectedColor = getOppositeColor(this.midgroundColor); } regenerateCaches(); } protected Color getOppositeColor(Color c) { int red = findOppositeNumber(c.getRed()); int green = findOppositeNumber(c.getGreen()); int blue = findOppositeNumber(c.getBlue()); return new Color(null, red, green, blue); } @Override synchronized public void setSize(int w, int h) { // Mark the caches as dirty so that no one will use them synchronized(this.flagLock) { this.cachedMidgroundDirty = true; this.cachedBackgroundDirty = true; } super.setSize(w, h); this.renderer.setSize(w, h); this.renderer.updateData(); regenerateCaches(); } // Drawing stuff public Color getSelectionColor() { return isSelected() ? this.selectedColor : this.midgroundColor; } public static int findOppositeNumber(int num) { return 255 - num; } @Override public void paint(Graphics g) { Rectangle bds = getBounds(); g.pushState(); try { // Always draw the background skin // First pass -- commented out to keep checkins separate. int oldAlpha = g.getAlpha(); g.setAlpha(255); g.translate(bds.x, bds.y); this.renderer.paintBackground(g); g.translate(- bds.x, - bds.y); g.setAlpha(oldAlpha); // Check if we should perform shortcut rendering boolean shortcut; synchronized(this.flagLock) { shortcut = this.fast || this.cachedMidgroundDirty || this.cachedBackgroundDirty; } Color midColor = getSelectionColor(); int midgroundAlpha = displayAlpha.determineAlpha(false); if (shortcut) { g.setBackgroundColor(midColor); g.setAlpha(midgroundAlpha); this.clipper.fillShape(g, bds); } else { // Otherwise we render everything properly // Lock the caches while we use them synchronized(GraphicalWidgetBase.this) { // Render the backgound image if necessary if (this.cachedBackground != null) { g.drawImage(this.cachedBackground, bds.x, bds.y); } // XXX: Workaround for the dysfunctional transparency // support with Graphics objects on Windows platforms // Most of the work-around for this issue is in // regenerateCaches and in the clipper. // There is also a quick workaround in set selected // This entire "block" can be removed, but // to protect against fixes in SWT causing issues, I // am leaving it here. if (! OSUtils.WINDOWS) { // Render the midground with the appropriate level of transparency g.setAlpha(midgroundAlpha); } g.setBackgroundColor(midColor); // Image img = new Image(null, this.cachedMidgroundData); g.drawImage(this.cachedMidground, bds.x, bds.y); // img.dispose(); } } // Always draw the foreground text g.setAlpha(255); g.translate(bds.x, bds.y); this.renderer.paintForeground(g); // If this is a remote label, indicate by setting a dotted border if (null != model.getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR)) { int oldLineStyle = g.getLineStyle(); g.setLineStyle(Graphics.LINE_DOT); g.drawRectangle(0, 0, bds.width - 1, bds.height - 1); g.setLineStyle(oldLineStyle); } } // catch (Exception e) { // RcvrExceptionHandler. // } finally { // Return the canvas to its original state g.popState(); } } // paint /** * Regenerates the dirty caches in a background thread. */ protected void regenerateCaches() { Dimension size = getSize(); regenerateCaches(size.width, size.height); } protected void regenerateCaches(final int width, final int height) { // For performance reasons, we defer regeneration until we exit fast mode if (! this.fast) { if (View.isDrawingOK()) { // Recompute the cached data in the background // new Thread () { // public void run() { boolean needsRepaint = false; // Lock the parameters & caches while we use/manipulate them // synchronized(GraphicalWidget.this) { // Recompute the midground cache if it is dirty if (cachedMidgroundDirty) { needsRepaint = true; if ((width == 0) || (height == 0)) { throw new IllegalArgumentException( "Width or Height on a widget is 0. This " + "should have been prevented in setShape " + "Size"); } // Create a temporary canvas on which to draw the midground Image img = new Image(null, width, height); GC gc = new GC(img); SWTGraphics g = new SWTGraphics(gc); g.setAntialias(SWT.OFF); // Use the renderer & clipper to paint the midground nicely g.setBackgroundColor(getSelectionColor()); renderer.paintMidground(g); // XXX: Fix for Windows not correctly handling // Alpha's on images, when setting an alpha // on the GC. // This updates the Alpha value for all pixels // on the image, to use the selected widget // value. if (OSUtils.WINDOWS) { ImageData id = img.getImageData(); id.alpha = displayAlpha.determineAlpha(false); img.dispose(); img = new Image(null, id); } // WARNING: this might dispose img, or it might return it // DO NOT DISPOSE img! Image newFG = clipper.clip(img); // Clean the cache cachedMidgroundDirty = false; cachedMidground.dispose(); cachedMidground = newFG; // cachedMidgroundData = newFG.getImageData(); // Dispose of temporary resources // newFG.dispose(); g.dispose(); gc.dispose(); } // Recompute the background cache if it is dirty if (cachedBackgroundDirty) { needsRepaint = true; Image newBG = null; // Check if background is present byte[] bgImageData = model.getImage(); if (bgImageData != null) { ImageData bg = new ImageData( new ByteArrayInputStream(bgImageData)); newBG = clipper.clip(new Image(null, bg.scaledTo(width, height))); } // Clean the cache cachedBackgroundDirty = false; if (cachedBackground != null) { cachedBackground.dispose(); } cachedBackground = newBG; } // } // Make sure the results of our update are shown if (needsRepaint && View.isDrawingOK()) { // WindowUtil.globalDisplay.syncExec( // new Runnable() { // public void run() { repaint(); // } // } // ); } // } // }.start(); } } } /** * Dispose of all resources */ @Override synchronized public void dispose() { if (this.widgetColor != GraphicsUtil.DEFAULT_COLOR) { this.widgetColor.dispose(); } this.cachedMidground.dispose(); if (this.cachedBackground != null) { this.cachedBackground.dispose(); } model.removeAllHandlers(this); if (this.attrOverride != null) { this.attrOverride.removeAllHandlers(this); } super.dispose(); } /** * Adjusts the speed-quality tradeoff for rendering * @param on true to optimize for speed; false for quality */ public void setFastMode(boolean on) { this.fast = on; // Make everything look nice again regenerateCaches(); } }